Skip to content

feat(core): persist assistant message metadata in memory#1183

Merged
omeraplak merged 4 commits intomainfrom
feat/message-metadata-persistence
Apr 1, 2026
Merged

feat(core): persist assistant message metadata in memory#1183
omeraplak merged 4 commits intomainfrom
feat/message-metadata-persistence

Conversation

@omeraplak
Copy link
Copy Markdown
Member

@omeraplak omeraplak commented Apr 1, 2026

PR Checklist

Please check if your PR fulfills the following requirements:

Bugs / Features

What is the current behavior?

messageMetadata only affects the live UI stream. When messages are later fetched from memory, UIMessage.metadata only contains the default persisted metadata such as operationId.

What is the new behavior?

messageMetadataPersistence can now be configured at the agent level or per call via memory.options.messageMetadataPersistence (or the legacy top-level messageMetadataPersistence). When enabled, VoltAgent persists assistant usage and/or finishReason into memory-backed UIMessage.metadata for generateText, streamText, generateObject, and streamObject.

fixes (issue)

N/A

Notes for reviewers

  • The new persistence helper is applied before memory flush in text flows and directly on saved UIMessages in object flows.
  • Nested tool/agent calls now inherit messageMetadataPersistence through resolved memory overrides.
  • Verified with pnpm vitest run src/agent/agent.spec.ts --typecheck in packages/core.
  • Verified with pnpm vitest run src/schemas/agent.schemas.spec.ts in packages/server-core.
  • Verified with pnpm build in packages/core.
  • Verified with pnpm build in packages/server-core.

Summary by CodeRabbit

  • New Features

    • Assistant message metadata (usage tokens and finish reason) can now be persisted to memory for richer conversation context.
    • Configurable per-agent or per-request with fine-grained toggles for which metadata fields to store; legacy top-level setting is deprecated in favor of memory.options.
  • Tests

    • Added coverage verifying metadata persistence for generated and streamed assistant responses.
  • Documentation

    • Integration and API docs updated with examples showing how to enable and retrieve persisted message metadata.

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 1, 2026

🦋 Changeset detected

Latest commit: c8378ed

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@voltagent/core Patch
@voltagent/server-core Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@joggrbot

This comment has been minimized.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 1, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6670a66b-a386-413b-8057-860c813af8b7

📥 Commits

Reviewing files that changed from the base of the PR and between ff1f7cf and c8378ed.

📒 Files selected for processing (1)
  • .changeset/funny-ravens-wave.md

📝 Walkthrough

Walkthrough

Adds an option to persist assistant message metadata (usage and finishReason) into memory-backed UIMessage.metadata; includes type/schema support, runtime option resolution, agent persistence logic for generate/stream flows, tests, changeset, and documentation updates.

Changes

Cohort / File(s) Summary
Types & Runtime Options
packages/core/src/agent/types.ts
Added AgentMessageMetadataPersistenceOptions and AgentMessageMetadataPersistenceConfig; added messageMetadataPersistence to AgentOptions, runtime memory option interfaces, and deprecated per-call CommonGenerateOptions field.
Core Agent Logic
packages/core/src/agent/agent.ts
Normalize/resolve messageMetadataPersistence; build persisted metadata (usage, finishReason, operationId) and attach to assistant UIMessage on generate/stream completion; fallback insertion when attachment fails; propagate resolved memory options through context.
Schemas & Validation
packages/server-core/src/schemas/agent.schemas.ts, packages/server-core/src/schemas/agent.schemas.spec.ts
Added Zod schemas for MessageMetadataPersistenceOptionsSchema / MessageMetadataPersistenceConfigSchema; integrated into RuntimeMemoryBehaviorOptionsSchema; accept boolean legacy shape; added schema tests for object and boolean forms.
Tests
packages/core/src/agent/agent.spec.ts
New memory integration tests for generateText and streamText verifying persisted operationId, mapped usage fields, and finishReason; expanded operation-context resolution assertions.
Docs
website/docs/ui/ai-sdk-integration.md, website/docs/api/endpoints/agents.md
Documented memory.options.messageMetadataPersistence (boolean
Release Metadata
.changeset/funny-ravens-wave.md
Added changeset entry marking patch releases for @voltagent/core and @voltagent/server-core describing the new metadata persistence feature.

Sequence Diagram(s)

sequenceDiagram
  participant Client as Client
  participant Agent as Agent
  participant Provider as ModelProvider
  participant Memory as MemoryStore

  Client->>Agent: generateText / streamText (with memory.options.messageMetadataPersistence)
  Agent->>Provider: invoke model generation / stream
  Provider-->>Agent: response (+ provider usage, finishReason on finish)
  Agent->>Agent: build persistedMetadata { operationId, usage, finishReason }
  Agent->>Memory: persist assistant UIMessage with metadata
  Memory-->>Agent: ack saved message
  Agent-->>Client: return response (stream or final)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Possibly related PRs

Poem

🐇 I hopped through tokens, counting each small bite,

I tucked finish reasons and usage into night.
Now messages remember what the model chose,
In memory's soft burrow, each metadata rose.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and concisely describes the main feature: persisting assistant message metadata in memory, which aligns with the primary change across all modified files.
Description check ✅ Passed The PR description follows the template structure, includes completed checklist items, explains current vs. new behavior, and provides implementation notes for reviewers.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/message-metadata-persistence

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages bot commented Apr 1, 2026

Deploying voltagent with  Cloudflare Pages  Cloudflare Pages

Latest commit: c8378ed
Status:⚡️  Build in progress...

View logs

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 7 files

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
packages/server-core/src/schemas/agent.schemas.spec.ts (1)

97-112: Add legacy top-level object-form test for parity.

The backward-compatibility test currently covers only boolean. Since the accepted type is boolean | object, add a case for the deprecated top-level object form too.

Suggested test addition
   it("keeps legacy top-level memory fields for backward compatibility", () => {
     const payload = {
       userId: "legacy-user",
       conversationId: "legacy-conv",
       contextLimit: 10,
       semanticMemory: {
         enabled: true,
       },
       conversationPersistence: {
         mode: "finish",
       },
       messageMetadataPersistence: true,
     };

     expect(() => GenerateOptionsSchema.parse(payload)).not.toThrow();
   });
+
+  it("accepts legacy top-level messageMetadataPersistence object form", () => {
+    const payload = {
+      userId: "legacy-user",
+      conversationId: "legacy-conv",
+      messageMetadataPersistence: {
+        usage: true,
+        finishReason: true,
+      },
+    };
+
+    expect(() => GenerateOptionsSchema.parse(payload)).not.toThrow();
+  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/server-core/src/schemas/agent.schemas.spec.ts` around lines 97 -
112, Add a new test case to cover the deprecated top-level object form of
messageMetadataPersistence: create a payload similar to the existing legacy test
but set messageMetadataPersistence to an object (e.g., { enabled: true }) and
assert GenerateOptionsSchema.parse(payload) does not throw; locate the test
suite around the existing "keeps legacy top-level memory fields for backward
compatibility" spec and add this new it(...) using GenerateOptionsSchema.parse
to validate parity with the boolean case.
website/docs/ui/ai-sdk-integration.md (1)

364-366: Consider documenting boolean semantics explicitly (true/false and default).

The table and paragraph are good, but adding one sentence clarifying what true persists, what false does, and the default behavior would reduce ambiguity.

Also applies to: 403-404

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@website/docs/ui/ai-sdk-integration.md` around lines 364 - 366, Update the
docs for memory.options.messageMetadataPersistence and its sub-properties
(memory.options.messageMetadataPersistence.usage and
memory.options.messageMetadataPersistence.finishReason) to include a single
sentence that states the boolean semantics: what happens when set to true
(persist the specified metadata), what happens when set to false (do not persist
it), and the default value when the option is omitted; ensure the sentence
appears alongside the existing table entries so readers see for each symbol
whether the default is true or false and the exact effect of true vs false.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/server-core/src/schemas/agent.schemas.spec.ts`:
- Around line 97-112: Add a new test case to cover the deprecated top-level
object form of messageMetadataPersistence: create a payload similar to the
existing legacy test but set messageMetadataPersistence to an object (e.g., {
enabled: true }) and assert GenerateOptionsSchema.parse(payload) does not throw;
locate the test suite around the existing "keeps legacy top-level memory fields
for backward compatibility" spec and add this new it(...) using
GenerateOptionsSchema.parse to validate parity with the boolean case.

In `@website/docs/ui/ai-sdk-integration.md`:
- Around line 364-366: Update the docs for
memory.options.messageMetadataPersistence and its sub-properties
(memory.options.messageMetadataPersistence.usage and
memory.options.messageMetadataPersistence.finishReason) to include a single
sentence that states the boolean semantics: what happens when set to true
(persist the specified metadata), what happens when set to false (do not persist
it), and the default value when the option is omitted; ensure the sentence
appears alongside the existing table entries so readers see for each symbol
whether the default is true or false and the exact effect of true vs false.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 097b7f21-8470-4675-ae75-852936105616

📥 Commits

Reviewing files that changed from the base of the PR and between 90b655d and 5289ade.

📒 Files selected for processing (7)
  • .changeset/funny-ravens-wave.md
  • packages/core/src/agent/agent.spec.ts
  • packages/core/src/agent/agent.ts
  • packages/core/src/agent/types.ts
  • packages/server-core/src/schemas/agent.schemas.spec.ts
  • packages/server-core/src/schemas/agent.schemas.ts
  • website/docs/ui/ai-sdk-integration.md

@omeraplak omeraplak merged commit b48f107 into main Apr 1, 2026
22 of 24 checks passed
@omeraplak omeraplak deleted the feat/message-metadata-persistence branch April 1, 2026 17:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant